package panda.record;

import panda.file.Block;
import panda.file.Page;
import panda.transaction.Transaction;

public class RecordFile {
	private TableToken tableToken;

	private Transaction transaction;

	private String fileName;

	private RecordPage recordPage;

	private int currentBlockNumber;

	public RecordFile(TableToken tableToken, Transaction transaction) {
		this.tableToken = tableToken;
		this.transaction = transaction;
		fileName = tableToken.getFilename();
		if (transaction.getSize(fileName) == 0)
			appendBlock();
		moveTo(0);
	}

	public void close() {
		recordPage.close();
	}

	public void rewind() {
		moveTo(0);
	}

	public boolean next() {
		while (true) {
			if (recordPage.next())
				return true;
			if (atLastBlock()) {
				return false;
			}
			moveTo(currentBlockNumber + 1);
		}
	}

	public int getInt(String fieldName) {
		return recordPage.getInt(fieldName);
	}

	public String getString(String fieldName) {
		return recordPage.getString(fieldName);
	}

	public float getFloat(String fieldName) {
		return recordPage.getFloat(fieldName);
	}

	public long getLong(String fieldName) {
		return recordPage.getLong(fieldName);
	}

	public byte[] getArray(String fieldName) {
		return recordPage.getArray(fieldName);
	}

	public boolean isNull(String fieldName) {
		return recordPage.isNull(fieldName);
	}

	public void setInt(String fieldName, int value) {
		recordPage.setInt(fieldName, value);
	}

	public void setString(String fieldName, String value) {
		recordPage.setString(fieldName, value);
	}

	public void setFloat(String fieldName, float value) {
		recordPage.setFloat(fieldName, value);
	}

	public void setLong(String fieldName, long value) {
		recordPage.setLong(fieldName, value);
	}

	public void setArray(String fieldName, byte[] value) {
		recordPage.setArray(fieldName, value);
	}

	public void setNull(String fieldName) {
		recordPage.setNull(fieldName);
	}

	public void delete() {
		if (recordPage.isAvailable()) {
			recordPage.delete();
		} else {
			recordPage.delete();
			addToChain(currentBlockNumber);
		}
	}

	public void addToChain(int blockNumber) {
		int position = 0;
		if (blockNumber != 0) {
			position = recordPage.getCurrentPosition();
		}
		moveTo(0);
		int pointer = recordPage.getAvailableChainPointer();
		recordPage.setAvailableChainPointer(blockNumber);
		moveTo(blockNumber);
		recordPage.addToChain(pointer);
		if (blockNumber != 0) {
			recordPage.moveTo(position);
		}
	}

	public void insert() {
		moveTo(0);
		if (!recordPage.insert()) {
			int nextAvailablePage = recordPage.nextAvailablePage();
			if (nextAvailablePage == 0) {
				appendBlock();
				moveTo(transaction.getSize(fileName) - 1);
			} else {
				moveTo(nextAvailablePage);
			}
			recordPage.insert();
			if (!recordPage.isAvailable()) {
				int blockNumber = currentBlockNumber;
				int position = recordPage.getCurrentPosition();
				int pointer = recordPage.removeFromAvailableChain();
				moveTo(0);
				recordPage.setAvailableChainPointer(pointer);
				moveTo(blockNumber);
				recordPage.moveTo(position);
			}
		}
		/*
		 * while (!recordPage.insert()) { if (atLastBlock()) appendBlock();
		 * moveTo(currentBlockNumber + 1); }
		 */
	}

	// TODO Please modify this according to the following requirment
	/**
	 * Set the cursor to the specified tuple
	 * 
	 * @param tt
	 *            the token of the tuple
	 */
	public void setCursor(TupleToken tt) {
		moveTo(tt.getBlockNum());
		recordPage.moveTo(tt.getOffSet());
	}

	// TODO Please modify this according to the following requirment
	/**
	 * Get the token of the tuple pointed to by the cursor
	 * 
	 * @return the token
	 */
	public TupleToken getCursor() {
		int offset = recordPage.getCurrentPosition();
		return new TupleToken(currentBlockNumber, offset);
	}

	private void moveTo(int blockNumber) {
		if (recordPage != null)
			recordPage.close();
		currentBlockNumber = blockNumber;
		Block block = new Block(fileName, currentBlockNumber);
		recordPage = new RecordPage(block, tableToken, transaction);
	}

	private boolean atLastBlock() {
		return currentBlockNumber == transaction.getSize(fileName) - 1;
	}

	private void appendBlock() {
		RecordFormatter recordFormatter = new RecordFormatter(tableToken);
		transaction.append(fileName, recordFormatter);
		addToChain(transaction.getSize(fileName) - 1);
	}
}
